跳到主要内容

Vue前端路由

前置准备

参考资料 云中桥「前端进阶」彻底弄懂前端路由

路由是一个比较广义和抽象的概念,路由的本质就是对应关系;开发中,路由分为 后端路由、前端路由

后端路由

根据不同的用户 URL 请求,返回不同的内容。就是 URL 请求地址与服务器资源之间的对应关系

SPA 技术

SPA(Single page Application)单页面应用程序:就是一个 WEB 项目只有一个 HTML 页面,一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转。取而代之的是利用 JS 动态的变换(Ajax 局部更新) HTML 的内容,从而来模拟多个视图间跳转。例如一些固定的页头和页尾是不变的,只有内容变更了。所以只需动态的替换中间的内容就好了

但由于 SPA 中用户的交互是通过 JS 改变 HTML 内容来实现的,页面本身的 url 并没有变化,这导致了两个问题:

1、SPA 无法记住用户的操作记录,无论是刷新、前进还是后退,都无法展示用户真实的期望内容。

2、SPA 中虽然由于业务的不同会有多种页面展示形式,但只有一个 url,对 SEO 不友好,不方便搜索引擎进行收录。

所以就引入了前端路由的技术

前端路由

简单的说,就是在保证只有一个 HTML 页面,且与用户交互时不刷新和跳转页面的同时,为 SPA 中的每个视图展示形式匹配一个特殊的 url。在刷新、前进、后退和SEO时均通过这个特殊的 url 来实现。

为实现这一目标,需要做到以下二点:

1、改变 url 且不让浏览器向服务器发送请求。

2、可以监听到 url 的变化

实现原理:基于 URL 地址的 hash (就是锚点),hash的变化会导致浏览器访问记录的变化、但是hash的变化不会触发新的 URL 请求

就是 URL 后面跟着的 # 一般用于页面内跳转

http://localhost/data/#abc

实现简易的前端路由

基于 URL 中的 hash 实现(点击菜单的时候改变URL的hash,根据hash的变化控制组件的切换)

ataXgs.png

使用 Window 对象的 onhashchange 事件,根据获取到的最新的 hash 值,切换要显示的组件的名称

window.onhashchange = function(){
// 通过 location.hash 获取到最新的 hash 值
}

配置环境

在 Vue 后面加载 vue-router,它会自动安装的:

<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>

如果是 NPM

npm install vue-router

如果在一个模块化工程中使用它,必须要通过 Vue.use() 明确地安装路由功能:

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

Vue Router包含的功能有:

  • 支持 HTML5 历史模式或 hash 模式
  • 支持嵌套路由
  • 支持路由参数
  • 支持编程式路由
  • 支持命名路由

前端路由基本使用

基本使用步骤

1、引入库文件

<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>

2、添加路由链接

<!-- router-link 是 vue 中提供的标签,默认会被渲染成 a 标签 -->
<!-- to 属性默认会被渲染成 href 属性 -->
<!-- to 属性的值默认会被渲染成 # 开头的 hash 地址 -->
<router-link to="/user">User</router-link>
<router-link to="/register">Register</router-link>

3、添加路由填充位

<!-- 路由填充位(也叫路由占位符) -->
<!-- 将来通过路由规则匹配到的组件,将会被渲染到 router-view 所在位置 -->
<router-view></router-view>

4、定义路由组件

const User = {
template: '<h1>User 组件</h1>'
}

const Register = {
template: '<h1>Register 组件</h1>'
}

5、配置路由规则并创建路由实例

// 创建路由实例
const router = new VueRouter({
// 路由规则数组
routes: [
// 每个路由规则都是一个配置对象,其中至少包含 path 和 component 两个属性
// path 表示当前路由规则匹配的 hash 地址
// component 表示当前路由规则对应要展示的组件
{ path: '/user', component: User },
{ path: '/register', component: Register }
]
})

6、把路由挂载到 Vue 根实例中

new Vue({
el: '#app',
// 挂载到 Vue 实例对象上
router: router // 属性和属性值一样可以简写成:router
})

路由重定向

路由重定向指的是:用户在访问地址 A 的时候,强制用户跳转到地址 C,从而展示特定的组件页面

通过路由规则的 redirect 属性,指定一个新的路由地址

// 创建路由实例
const router = new VueRouter({
// 路由规则数组
routes: [
// path 表示需要被重定向的原地址,redirect 表示要被重定向到的新地址
{ path: '/', redirect: '/user' },
{ path: '/user', component: User },
{ path: '/register', component: Register }
]
})

嵌套路由

点击父级路由链接显示模板内容,模板内容中又有子路由链接 atog39.png

只需在模板里加上就好了

const Register = {
template: `
<div>
<h1>Register 组件</h1>
<hr/>
<router-link to="/register/table1">Table1</router-link>
<router-link to="/register/table2">Table2</router-link>

<!-- 子路由填充位 -->
<router-view></router-view>
</div>
`
}


const Table1 = {
template: '<h3>Table1 子组件</h3>'
}


const Table2 = {
template: '<h3>Table2 子组件</h3>'
}


// 创建路由实例
const router = new VueRouter({
// 路由规则数组
routes: [
// path 表示需要被重定向的原地址,redirect 表示要被重定向到的新地址
{ path: '/', redirect: '/user' },
{ path: '/user', component: User },
{
path: '/register', component: Register, children: [
// 设置子路由规则
{ path: '/register/table1', component: Table1 },
{ path: '/register/table2', component: Table2 },
]
}
]
})

编程式导航

导航有两种方式:

1、声明式导航:通过点击链接实现导航的方式,叫做声明式导航 例如:普通网页中的 <a></a> 链接 或 vue 中的 router-link

2、编程式导航:通过调用 JavaScript 形式的 API 实现导航的方式,叫做编程式导航 例如:普通网页中的 location.href

常用的编程式导航 API

this.$router.push('hash地址')
// 传递一个数字,1表示前进,-1表示后退
this.$router.go(number)

例如点击按钮跳转到注册页面

const User = {
template: '<div><button @click="goRegister">跳转到注册页面</button></div>',
methods: {
// 注意这里不能用箭头函数
goRegister:function(){
this.$router.push('/register')
}
},
}

router.push() 方法的参数规则

除了上面直接写路由字符串的方式,还能传递参数进去

// 字符串(路径名称)
router.push('/home')

// 对象
router.push({path: '/home'})

// 命名的路由(传递参数)
router.push({name: 'home', params: {userId: 123}})

// 带查询参数,变成 /register?uname=lisa
router.push({path: '/home', query: {uname: 'lisa'}})

动态路由

动态路由匹配

当某些路由一部分是完全一样的,只有小部分参数是动态变化的时可以用

<router-link to="/user/1">User1</router-link>
<router-link to="/user/2">User2</router-link>
<router-link to="/user/3">User3</router-link>

<router-view></router-view>
const User = {
// 路由组件中通过 $route.params 获取路由参数
template: '<div>User {{ $route.params.id }}</div>'
}

const router = new VueRouter({
routes: [
// 动态路径参数 以冒号开头
{ path: '/user/:id', component: User }
]
})

路由组件传递参数

前面的 $route.params 和对应路由形成高耦合。所以可以使用 props 将组件和路由解耦

注:这个 props 是 Vue 组件化开发的内容了,不了解的回顾去

因为路由本质就是点击了之后在某块地方填充组件嘛,所以以前怎么传递组件参数的,这里一样可以使用

当 props 的值是 Boolean 类型时

const router = new VueRouter({
routes: [
// 如果 props 被设置为 true,route.params 将会被设置为组件属性
// 开启 props 传参
{ path: '/user/:id', component: User , props: true}
]
})

const User = {
// 使用 props 接收路由参数
props: ['id'],
// 使用路由参数
template: '<div>User 的 ID 为:{{ id }}</div>'
}

当 props 的值是对象类型时

它会按原样设置为组件属性,但是这时的 id 是访问不到的

const router = new VueRouter({
routes: [
// 如果 props 是一个对象,它会按原样设置为组件属性,但是这时的 id 是访问不到的(就算是下面的 props 有接收这个参数)
{ path: '/user/:id', component: User , props: {uname: 'list', age: 12}}
]
})

const User = {
// 使用 props 接收路由参数
props: ['uname', 'age'],
// 使用路由参数
template: "<div>用户信息为:{{ uname + '----' + age}}</div>"
}

props 的值为函数类型时

这种方式可以既传递 id 又传递 “对象”

const router = new VueRouter({
routes: [
// 如果 props 是一个对象,则这个函数接收 router 对象为形参
// 注意这个 route =>({ uname: '张三', age: 20 ,id: route.params.id }) 表示 return { uname: '张三', age: 20 ,id: route.params.id } 对象
{ path: '/user/:id', component: User , props: route =>({
uname: '张三', age: 20 ,id: route.params.id
})}
]
})

const User = {
// 使用 props 接收路由参数
props: ['uname', 'age' ,'id'],
// 使用路由参数
template: "<div>用户信息为:{{ uname + '----' + age + '----' + id}}</div>"
}

命名路由的匹配规则

为了更加方便的表示路由的路径,可以给路由规则起一个别名,即为“命名路由”

const router = new VueRouter({
routes: [
{ path: '/user/:id', name: 'user' ,component: User }
]
})

然后在 router-link 里绑定这个命名

<!-- 注意 to 前面要加冒号 -->
<router-link :to="{name: 'user', params: {id: 123}}">User</router-link>

路由导航守卫

控制登陆权限,当没有登陆时禁止访问特定页面,需要重新导航到登陆页面

// 为路由对象添加 beforeEach 导航守卫
router.beforeEach((to, from, next) =>{
// 如果用户访问的登录页,直接放行
if(to.path === '/login') return next()

// 否则需要从 sessionStorage 中取得保存的 token 值
const tokenStr = window.sessionStorage.getItem('token')

// 没有 token,强制跳转到登录页
if(!tokenStr) return next('/login')

next()
})

基于 token 的方式退出只需销毁本地的 token 即可。这样后续的请求就不会携带 token,必须重新登陆生成一个新的 token 之后才能访问页面

// 清空 token
window.sessionStorage.clear()

// 跳转到登陆页
this.$router.push('/login')

路由案例

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<title>基于vue-router的案例</title>
<style type="text/css">
html,
body,
#app {
margin: 0;
padding: 0px;
height: 100%;
}

.header {
height: 50px;
background-color: #545c64;
line-height: 50px;
text-align: center;
font-size: 24px;
color: #fff;
}

.footer {
height: 40px;
line-height: 40px;
background-color: #888;
position: absolute;
bottom: 0;
width: 100%;
text-align: center;
color: #fff;
}

.main {
display: flex;
position: absolute;
top: 50px;
bottom: 40px;
width: 100%;
}

.content {
flex: 1;
text-align: center;
height: 100%;
}

.left {
flex: 0 0 20%;
background-color: #545c64;
}

.left a {
color: white;
text-decoration: none;
}

.right {
margin: 5px;
}

.btns {
width: 100%;
height: 35px;
line-height: 35px;
background-color: #f5f5f5;
text-align: left;
padding-left: 10px;
box-sizing: border-box;
}

button {
height: 30px;
background-color: #ecf5ff;
border: 1px solid lightskyblue;
font-size: 12px;
padding: 0 20px;
}

.main-content {
margin-top: 10px;
}

ul {
margin: 0;
padding: 0;
list-style: none;
}

ul li {
height: 45px;
line-height: 45px;
background-color: #a0a0a0;
color: #fff;
cursor: pointer;
border-bottom: 1px solid #fff;
}

table {
width: 100%;
border-collapse: collapse;
}

td,
th {
border: 1px solid #eee;
line-height: 35px;
font-size: 12px;
}

th {
background-color: #ddd;
}
</style>
</head>

<body>
<div>
<div id="app">
<router-view></router-view>
</div>



<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<script>
const APP = {
template: `
<div>
<!-- 头部区域 -->
<header class="header">传智后台管理系统</header>
<!-- 中间主体区域 -->
<div class="main">
<!-- 左侧菜单栏 -->
<div class="content left">
<ul>
<li><router-link to="/users">用户管理</router-link></li>
<li><router-link to="/rights">权限管理</router-link></li>
<li><router-link to="/goods">商品管理</router-link></li>
<li><router-link to="/orders">订单管理</router-link></li>
<li><router-link to="/setting">系统设置</router-link></li>
</ul>
</div>
<!-- 右侧内容区域 -->
<div class="content right">
<div class="main-content">
<router-view/>
</div>
</div>
</div>
<!-- 尾部区域 -->
<footer class="footer">版权信息</footer>
</div>
`
}

const User = {
data() {
return {
userlist: [
{ id: 1, name: '张三', age: 10 },
{ id: 2, name: '李四', age: 15 },
{ id: 3, name: '王五', age: 20 },
{ id: 4, name: '赵六', age: 25 }
]
}
},
methods: {
goDetail(id) {
console.log(id);
// 动态参数
this.$router.push(`/userInfo/${id}`)
}
},
template: `
<div>
<h3>用户管理区域</h3>
<table>
<thead>
<tr>
<th>编号</th>
<th>姓名</th>
<th>年龄</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in userlist" :key="index">
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.age}}</td>
<td><a href="Javascript:;" @click="goDetail(item.id)">详情</a></td>
</tr>
</tbody>
</table>
</div>
`
}

const UserInfo = {
props:['id'],
methods: {
goBack(){
this.$router.go(-1)
}
},
template: `
<div>
<h5>用户详情页 -- {{id}}</h5>
<button @click="goBack()">后退</button>
</div>
`
}

const Rights = {
template: `
<div>
<h3>权限管理区域</h3>
</div>
`
}

const Goods = {
template: `
<div>
<h3>商品管理区域</h3>
</div>
`
}

const Orders = {
template: `
<div>
<h3>订单管理区域</h3>
</div>
`
}

const Setting = {
template: `
<div>
<h3>系统设置区域</h3>
</div>
`
}

const router = new VueRouter({
routes: [{
path: '/',
component: APP,
redirect: '/users', // 路由重定向
children: [
{ path: '/users', component: User },
{ path: '/userInfo/:id', component: UserInfo, props: true },
{ path: '/rights', component: Rights },
{ path: '/goods', component: Goods },
{ path: '/orders', component: Orders },
{ path: '/setting', component: Setting },
]
}]
})

const vm = new Vue({
el: '#app',
router: router
})
</script>
</div>
</body>

</html>